#include "RenderScriptEngine.h"
#include <angelscript.h>
#include <scriptbuilder\scriptbuilder.h>
#include <scriptstdstring\scriptstdstring.h>
#include <scriptarray\scriptarray.h>
#include <scriptmath\scriptmath.h>
#include <wx\wx.h>

#include "..\Rendering\ITexture.h"
#include "..\Math\Point2.h"
#include "..\Math\Point3.h"
#include "..\Rendering\Box.h"
#include "..\Rendering\Face.h"
#include "..\Schematics\Schematic.h"
#include "..\Schematics\Block.h"
#include "..\Rendering\RendererBase.h"
#include "..\Rendering\BlockRenderers.h"

#include <wx\dir.h>

ModuleInfo *_currentInfo = nullptr;
// Implement a simple message callback function
void MessageCallback(const asSMessageInfo *msg, void *param)
{
	if (_currentInfo == nullptr)
		return;

	const char *type = "ERR";
	if( msg->type == asMSGTYPE_WARNING ) 
		type = "WARN";
	else if( msg->type == asMSGTYPE_INFORMATION ) 
		type = "INFO";

	wxString str;
	str.Printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message);
	_currentInfo->Errors += str.c_str().AsChar();
}

static void ConstructPoint2i(const int &x, const int &y, Point2i *ptr)
{
	new(ptr) Point2i(x, y);
}

static void ConstructPoint2iC(const Point2i &v, Point2i *ptr)
{
	new(ptr) Point2i(v);
}

static void ConstructPoint3i(const int &x, const int &y, const int &z, Point3i *ptr)
{
	new(ptr) Point3i(x, y, z);
}

static void ConstructPoint3iC(const Point3i &v, Point3i *ptr)
{
	new(ptr) Point3i(v);
}

static void ConstructPoint3f(const float &x, const float &y, const float &z, Point3i *ptr)
{
	new(ptr) Point3f(x, y, z);
}

static void ConstructPoint3fC(const Point3f &f, Point3f *ptr)
{
	new(ptr) Point3f(f);
}

static void ConstructColorb(const byte &r, const byte &g, const byte &b, const byte &a, Colorb *ptr)
{
	new(ptr) Colorb(r, g, b, a);
}

static void ConstructColorbC(const Colorb &c, Colorb *ptr)
{
	new(ptr) Colorb(c);
}

static void ConstructModuleInfo(const std::string &name, const std::string &friendlyName, const std::string &version, const std::string &author, const std::string &dependencies, ModuleInfo *ptr)
{
	new(ptr) ModuleInfo(name, friendlyName, version, author, dependencies);
}

static void ConstructModuleInfoDefault(ModuleInfo *ptr)
{
	new(ptr) ModuleInfo();
}

static void DestructModuleInfo(ModuleInfo *ptr)
{
	ptr->~ModuleInfo();
}

static void ConstructBox3(ITexture *texture, const Point3i &bounds, const Point3i &pos, Box *box)
{
	new(box) Box(texture, bounds, pos);
}

static void ConstructBox0(Box *box)
{
	new(box) Box(nullptr);
}

static void ConstructBoxC(const Box &left, Box *box)
{
	memcpy(box, &left, sizeof(Box));
}

static void ConstructFace(const Point3f &p0, const Point3f &p1, const Point3f &p2, const Point3f &p3, const Colorb &color, ITexture *texture, const Point2i &coords, const float &x, const float &y, Box *box)
{
	new(box) Face(p0, p1, p2, p3, color, texture, coords, x, y);
}

static void ConstructFaceC(const Face &left, Face *box)
{
	memcpy(box, &left, sizeof(Face));
}

static void ConstructBlockDef(Block *b)
{
	new(b) Block();
}

static void ConstructBlock2(const int &id, const byte &data, Block *b)
{
	new (b) Block(id, data);
}

static void ConstructBlockC(const Block &b, Block *ptr)
{
	new(ptr) Block(b);
}

static void construct_ptr( void **ptr ){}
static void copy_construct_ptr( void **other, void **ptr ){ *ptr = *other; }
static void destruct_ptr( void *ptr ){ }

RenderScriptEngine *_currentEngine;
static void AddRenderer(const int &id, asIScriptObject *obj)
{
	_currentEngine->Renderers->AddRender(id, obj);
}

void RenderScriptEngine::Execute(Schematic *schematic, PrimitiveRendererBase *renderer, const Block &block, const Point3i &position, void *obj)
{
	asIScriptObject *fobj = (asIScriptObject*)obj;
	auto type = fobj->GetObjectType();
	auto f = type->GetMethodByName("Render");
	auto ctx = engine->CreateContext();
	ctx->Prepare(f);
	ctx->SetObject(fobj);

	ctx->SetArgObject(0, schematic);
	ctx->SetArgObject(1, renderer);
	ctx->SetArgObject(2, (void*)&block);
	ctx->SetArgObject(3, (void*)&position);
	ctx->Execute();

	ctx->Release();
}

bool RenderScriptEngine::ExecuteIsFullBlock(void *obj)
{
	asIScriptObject *fobj = (asIScriptObject*)obj;
	auto type = fobj->GetObjectType();
	auto f = type->GetMethodByName("IsFullBlock");

	if (f == nullptr)
		return true;

	auto ctx = engine->CreateContext();
	ctx->Prepare(f);
	ctx->SetObject(fobj);
	ctx->Execute();

	bool ret = ctx->GetReturnByte() != 0;

	ctx->Release();

	return ret;
}

bool RenderScriptEngine::ExecuteIsOpaqueBlock(void *obj)
{
	asIScriptObject *fobj = (asIScriptObject*)obj;
	auto type = fobj->GetObjectType();
	auto f = type->GetMethodByName("IsOpaqueBlock");

	if (f == nullptr)
		return true;

	auto ctx = engine->CreateContext();
	ctx->Prepare(f);
	ctx->SetObject(fobj);
	ctx->Execute();

	bool ret = ctx->GetReturnByte() != 0;

	ctx->Release();

	return ret;
}

void RenderScriptEngine::Release(void *obj)
{
	asIScriptObject *fobj = (asIScriptObject*)obj;

	fobj->Release();
}

static void ConstructModuleInfoCopy(const ModuleInfo &left, ModuleInfo *box)
{
	new(box) ModuleInfo(left);
}

asIScriptEngine *_engine;

bool IsModuleLoaded(const std::string &str)
{
	return _engine->GetModule(str.c_str(), asGM_ONLY_IF_EXISTS) != nullptr;
}

asIScriptObject *CreateModuleRenderer(const std::string &module, const std::string &rendererType)
{
	asIScriptModule *moduleInstance;

	if ((moduleInstance = _engine->GetModule(module.c_str(), asGM_ONLY_IF_EXISTS)) == nullptr)
		return nullptr;

	asIObjectType *type = _engine->GetObjectTypeById(moduleInstance->GetTypeIdByDecl(rendererType.c_str()));

	if (type == nullptr)
		return nullptr;

	// Get the factory function from the object type
	asIScriptFunction *factory = type->GetFactoryByDecl((rendererType + " @" + rendererType + "()").c_str());
	asIScriptContext *ctx = _engine->CreateContext();

	ctx->Prepare(factory);
	ctx->Execute();

	asIScriptObject *obj = *(asIScriptObject**)ctx->GetAddressOfReturnValue();
	obj->AddRef();
	ctx->Release();

	return obj;
}

RenderScriptEngine::RenderScriptEngine(BlockRenderers *renderers) :
	Renderers(renderers)
{
	// Register engine
	engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
	_engine = engine;
	engine->SetEngineProperty(asEP_DISALLOW_GLOBAL_VARS, 1);
	engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);
	RegisterStdString(engine);
	RegisterScriptArray(engine, true);
	RegisterScriptMath(engine);

	// Register primitives
	auto r = engine->RegisterObjectType("ITexture", sizeof( ITexture* ), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_PRIMITIVE); assert( r >= 0 );
	r = engine->RegisterObjectBehaviour("ITexture", asBEHAVE_CONSTRUCT,  "void f()",                	asFUNCTION( construct_ptr ), asCALL_CDECL_OBJLAST); assert( r >= 0 );
	r = engine->RegisterObjectBehaviour("ITexture", asBEHAVE_CONSTRUCT,  "void f(const ITexture &in)",	asFUNCTION( copy_construct_ptr ), asCALL_CDECL_OBJLAST); assert( r >= 0 );
	r = engine->RegisterObjectBehaviour("ITexture", asBEHAVE_DESTRUCT,   "void f()",                	asFUNCTION( destruct_ptr ),  asCALL_CDECL_OBJLAST); assert( r >= 0 );

	r = engine->RegisterObjectType("Point2i", sizeof(Point2i), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Point2i", asBEHAVE_CONSTRUCT, "void f(const int &in, const int &in)", asFUNCTION(ConstructPoint2i), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Point2i", asBEHAVE_CONSTRUCT, "void f(const Point2i &in)", asFUNCTION(ConstructPoint2iC), asCALL_CDECL_OBJLAST); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point2i", "int X", offsetof(Point2i, X)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point2i", "int Y", offsetof(Point2i, Y)); assert(r >= 0);
	
	r = engine->RegisterObjectType("Point3i", sizeof(Point3i), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Point3i", asBEHAVE_CONSTRUCT, "void f(const int &in, const int &in, const int &in)", asFUNCTION(ConstructPoint3i), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Point3i", asBEHAVE_CONSTRUCT, "void f(const Point3i &in)", asFUNCTION(ConstructPoint3iC), asCALL_CDECL_OBJLAST); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point3i", "int X", offsetof(Point3i, X)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point3i", "int Y", offsetof(Point3i, Y)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point3i", "int Z", offsetof(Point3i, Z)); assert(r >= 0);
	
	r = engine->RegisterObjectType("Point3f", sizeof(Point3f), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Point3f", asBEHAVE_CONSTRUCT, "void f(const float &in, const float &in, const float &in)", asFUNCTION(ConstructPoint3f), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Point3f", asBEHAVE_CONSTRUCT, "void f(const Point3f &in)", asFUNCTION(ConstructPoint3fC), asCALL_CDECL_OBJLAST); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point3f", "float X", offsetof(Point3f, X)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point3f", "float Y", offsetof(Point3f, Y)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Point3f", "float Z", offsetof(Point3f, Z)); assert(r >= 0);
	
	r = engine->RegisterObjectType("Colorb", sizeof(Colorb), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Colorb", asBEHAVE_CONSTRUCT, "void f(const uint8 &in, const uint8 &in, const uint8 &in, const uint8 &in)", asFUNCTION(ConstructColorb), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Colorb", asBEHAVE_CONSTRUCT, "void f(const Colorb &in)", asFUNCTION(ConstructColorbC), asCALL_CDECL_OBJLAST); assert(r >= 0);
    r = engine->RegisterObjectProperty("Colorb", "uint8 R", offsetof(Colorb, R)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Colorb", "uint8 G", offsetof(Colorb, G)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Colorb", "uint8 B", offsetof(Colorb, B)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Colorb", "uint8 A", offsetof(Colorb, A)); assert(r >= 0);

	r = engine->RegisterGlobalProperty("Colorb Colorb_White", (void*)&Colorb::White);
	
	r = engine->RegisterObjectType("Block", sizeof(Block), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CAK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Block", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructBlockDef), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Block", asBEHAVE_CONSTRUCT, "void f(const int &in, const uint8 &in)", asFUNCTION(ConstructBlock2), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Block", asBEHAVE_CONSTRUCT, "void f(const Block &in)", asFUNCTION(ConstructBlockC), asCALL_CDECL_OBJLAST); assert(r >= 0);
    r = engine->RegisterObjectProperty("Block", "int ID", offsetof(Block, ID)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Block", "uint8 Data", offsetof(Block, Data)); assert(r >= 0);
	r = engine->RegisterObjectMethod("Block", "bool IsEmpty()", asMETHOD(Block, IsEmpty), asCALL_THISCALL); assert(r >= 0);
	
	r = engine->RegisterObjectType("Box", sizeof(Box), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Box", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructBox0), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Box", asBEHAVE_CONSTRUCT, "void f(ITexture t, const Point3i &in b = Point3i(16, 16, 16), const Point3i &in p = Point3i(0, 0, 0))", asFUNCTION(ConstructBox3), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Box", asBEHAVE_CONSTRUCT, "void f(const Box &in)", asFUNCTION(ConstructBoxC), asCALL_CDECL_OBJLAST); assert(r >= 0);
    r = engine->RegisterObjectProperty("Box", "Point3i Bounds", offsetof(Box, Bounds)); assert(r >= 0);
    r = engine->RegisterObjectProperty("Box", "Point3i Position", offsetof(Box, Position)); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &Unwrap(const Point2i &in)", asMETHOD(Box, Unwrap), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetCoordsFromBounds(const Point2i &in)", asMETHOD(Box, SetCoordsFromBounds), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetIndex(const int &in, const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetIndex), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetRange(const int &in, const int &in, const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetRange), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetNorth(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetNorth), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetEast(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetEast), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetSouth(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetSouth), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetWest(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetWest), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetTop(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetTop), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetBottom(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetBottom), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetSides(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetSides), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetAll(const Point2i &in, const Colorb &in color = ::Colorb_White)", asMETHOD(Box, SetAll), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetInset(const int &in, const int &in color = ::Colorb_White)", asMETHOD(Box, SetInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetTopInset(const int &in)", asMETHOD(Box, SetTopInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetBottomInset(const int &in)", asMETHOD(Box, SetBottomInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetNorthInset(const int &in)", asMETHOD(Box, SetNorthInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetEastInset(const int &in)", asMETHOD(Box, SetEastInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetSouthInset(const int &in)", asMETHOD(Box, SetSouthInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetWestInset(const int &in)", asMETHOD(Box, SetWestInset), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Box", "Box &SetSidesInset(const int &in)", asMETHOD(Box, SetSidesInset), asCALL_THISCALL); assert(r >= 0);
	
	r = engine->RegisterObjectType("Face", sizeof(Face), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_CLASS_CK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Face", asBEHAVE_CONSTRUCT, "void f(const Point3f &in, const Point3f &in, const Point3f &in, const Point3f &in, const Colorb &in, ITexture, const Point2i &in, const float &in, const float &in)", asFUNCTION(ConstructFace), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("Face", asBEHAVE_CONSTRUCT, "void f(const Face &in)", asFUNCTION(ConstructFaceC), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectMethod("Face", "Face &FlipX()", asMETHOD(Face, FlipX), asCALL_THISCALL); assert(r >= 0);

	r = engine->RegisterObjectType("ModuleInfo", sizeof(ModuleInfo), asOBJ_VALUE | asOBJ_APP_CLASS_CDK); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("ModuleInfo", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructModuleInfoDefault), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("ModuleInfo", asBEHAVE_CONSTRUCT, "void f(const string &in, const string &in, const string &in, const string &in, const string &in)", asFUNCTION(ConstructModuleInfo), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("ModuleInfo", asBEHAVE_CONSTRUCT, "void f(const ModuleInfo &in)", asFUNCTION(ConstructModuleInfoCopy), asCALL_CDECL_OBJLAST); assert(r >= 0);
	r = engine->RegisterObjectBehaviour("ModuleInfo", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructModuleInfo), asCALL_CDECL_OBJLAST); assert(r >= 0);
	
	r = engine->RegisterObjectType("NBTTagBase", 0, asOBJ_REF | asOBJ_NOCOUNT); assert(r >= 0);
	r = engine->RegisterObjectMethod("NBTTagBase", "NBTTagBase &Find(const string &in)", asMETHOD(NBTTagBase, Find), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("NBTTagBase", "const uint8 &AsUint8()", asMETHOD(NBTTagBase, AsUint8), asCALL_THISCALL); assert(r >= 0);

	r = engine->RegisterObjectType("Schematic", 0, asOBJ_REF | asOBJ_NOCOUNT); assert(r >= 0);
	r = engine->RegisterObjectMethod("Schematic", "const Block &GetBlock(const int &in, const int &in, const int &in)", asMETHODPR(Schematic, GetBlock, (const int &, const int &, const int &), const Block&), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("Schematic", "NBTTagBase &GetTileEntity(const int &in, const int &in, const int &in)", asMETHOD(Schematic, GetTileEntity), asCALL_THISCALL); assert(r >= 0);

	r = engine->RegisterObjectType("RendererBase", 0, asOBJ_REF | asOBJ_NOCOUNT); assert(r >= 0);
	r = engine->RegisterObjectMethod("RendererBase", "ITexture GetTexture(const string &in)", asMETHOD(RendererBase, GetTexture), asCALL_THISCALL); assert(r >= 0);

	r = engine->RegisterObjectType("PrimitiveRendererBase", 0, asOBJ_REF | asOBJ_NOCOUNT); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void Draw(const Box &in)", asMETHODPR(PrimitiveRendererBase, Draw, (const Box &), void), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void Draw(const Face &in, const bool &in reverse = false)", asMETHODPR(PrimitiveRendererBase, Draw, (const Face &, const bool &), void), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void Translate(const float &in, const float &in, const float &in)", asMETHOD(PrimitiveRendererBase, Translate), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void Rotate(const float &in, const int &in, const int &in, const int &in)", asMETHOD(PrimitiveRendererBase, Rotate), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void Scale(const float &in, const float &in, const float &in)", asMETHOD(PrimitiveRendererBase, Scale), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void PushState()", asMETHOD(PrimitiveRendererBase, PushState), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void PopState()", asMETHOD(PrimitiveRendererBase, PopState), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "void ExecuteRenderer(Schematic &, const Block &in, const Point3i &in)", asMETHOD(PrimitiveRendererBase, ExecuteRenderer), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "bool IsFullBlockAt(const int &in, const int &in, const int &in)", asMETHOD(PrimitiveRendererBase, IsFullBlockAt), asCALL_THISCALL); assert(r >= 0);
	r = engine->RegisterObjectMethod("PrimitiveRendererBase", "bool IsOpaqueBlockAt(const int &in, const int &in, const int &in)", asMETHOD(PrimitiveRendererBase, IsOpaqueBlockAt), asCALL_THISCALL); assert(r >= 0);
	
	r = engine->RegisterInterface("IBlockRenderer"); assert(r >= 0);
	r = engine->RegisterInterfaceMethod("IBlockRenderer", "void Render(Schematic &, PrimitiveRendererBase &, const Block &in, const Point3i &in)"); assert(r >= 0);

	r = engine->RegisterGlobalFunction("void AddRenderer(const int &in, IBlockRenderer @)", asFUNCTION(AddRenderer), asCALL_CDECL); assert(r >= 0);
	r = engine->RegisterGlobalFunction("bool IsModuleLoaded(const string &in)", asFUNCTION(IsModuleLoaded), asCALL_CDECL); assert(r >= 0);
	r = engine->RegisterGlobalFunction("IBlockRenderer @CreateModuleRenderer(const string &in module, const string &in rendererType)", asFUNCTION(CreateModuleRenderer), asCALL_CDECL); assert(r >= 0);
}

RenderScriptEngine::~RenderScriptEngine()
{
	engine->Release();
}

class ScriptLoader : public wxDirTraverser
{
	CScriptBuilder &_module;

public:
    ScriptLoader(CScriptBuilder &module) :
		_module(module)
	{
	}

	virtual wxDirTraverseResult OnFile(const wxString& fileName)
    {
		if (!fileName.EndsWith(".as"))
			return wxDIR_CONTINUE;
		if (fileName.EndsWith("ModuleInfo.as"))
			return wxDIR_CONTINUE;

		wxFileStream file(fileName);

		if (file.IsOk())
		{			
			file.SeekI(0, wxFromEnd);
			size_t fileLen = file.TellI();
			file.SeekI(0, wxFromStart);

			char *data = new char[fileLen];
			file.Read(data, fileLen);
			file.Close();

			int r = _module.AddSectionFromMemory(fileName, data, fileLen); assert(r >= 0);
			delete data;
		}

		return wxDIR_CONTINUE;
    }

    virtual wxDirTraverseResult OnDir(const wxString& dirname)
    {
        return wxDIR_CONTINUE;
    }
};

void RenderScriptEngine::SetGlobal(const char *str, void *ptr)
{
	auto r = engine->RegisterGlobalProperty(str, ptr); assert(r >= 0);
}

#include <wx\wfstream.h>
#include <wx\tokenzr.h>
#include <wx\stream.h>

class wxasStreamWrapper : public asIBinaryStream
{
	wxFileStream *_stream;
public:

	wxasStreamWrapper(wxFileStream *stream) :
		_stream(stream)
	{
	}

	void Read(void *ptr, asUINT size)
	{
		_stream->Read(ptr, size);
	}

	void Write(const void *ptr, asUINT size)
	{
		_stream->Write(ptr, size);
	}
};

void RenderScriptEngine::BuildScripts(RendererBase *renderer)
{
	_currentEngine = this;

	wxDir dir("renderers");
	wxString fileName;
	int r;

	// Pass 1: Load module info
	bool cont = dir.GetFirst(&fileName, wxEmptyString, wxDIR_DIRS);
	while (cont)
	{
		// create a temporary module just for module info
		wxFileStream file("renderers\\" + fileName + "\\ModuleInfo.as");

		if (file.IsOk())
		{
			auto tempModule = engine->GetModule("__temp", asGM_ALWAYS_CREATE);

			file.SeekI(0, wxFromEnd);
			size_t fileLen = file.TellI();
			file.SeekI(0, wxFromStart);

			char *data = new char[fileLen];
			file.Read(data, fileLen);

			file.Close();

			r = tempModule->AddScriptSection("renderers\\" + fileName + "\\ModuleInfo.as", data, fileLen); assert(r >= 0);
			r = tempModule->Build();

			delete[] data;

			if (r == 0)
			{
				auto func = tempModule->GetFunctionByName("GetInfo");
				auto ctx = engine->CreateContext();
				ctx->Prepare(func);
				ctx->Execute();
				ModuleInfo copy = *(ModuleInfo*)ctx->GetReturnAddress();
				copy.Namespace = fileName;
				_modules.push_back(copy);
				ctx->Release();
			}
		}

		cont = dir.GetNext(&fileName);
	}

	engine->DiscardModule("__temp");

	// Pass 2: discard modules with missing dependencies
	for (auto it = _modules.begin(); it != _modules.end(); )
	{
		wxStringTokenizer tok((*it).Dependencies, ";");
		bool goodModule = true;

		while (tok.HasMoreTokens())
		{
			wxString token = tok.GetNextToken();
			bool foundModule = false;

			for (auto it2 = _modules.begin(); it2 != _modules.end(); ++it2)
			{
				if ((*it2).Friendly == token)
				{
					foundModule = true;
					break;
				}
			}

			if (!foundModule)
			{
				goodModule = false;
				break;
			}
		}

		if (!goodModule)
			(*it).Enabled = false;
		
		++it;
	}

	// Pass 3: load all files into their respective modules
	for (auto it = _modules.begin(); it != _modules.end(); )
	{
		auto &info = (*it);

		if (!info.Enabled)
			continue;
		
		_currentInfo = &info;
		/*if (wxFileExists("renderers\\" + info.Namespace + "\\__codecache.bin"))
		{
			wxFileStream fs("renderers\\" + info.Namespace + "\\__codecache.bin");
			wxasStreamWrapper wrapper(&fs);

			info.Module = engine->GetModule(info.Namespace.c_str(), asGM_ALWAYS_CREATE);
			info.Module->LoadByteCode(&wrapper);
		}
		else*/
		{
			CScriptBuilder builder;
			builder.StartNewModule(engine, info.Namespace.c_str());

			wxDir dir("renderers\\" + info.Namespace);
			dir.Traverse(ScriptLoader(builder), wxEmptyString, wxDIR_FILES);

			r = builder.BuildModule(); assert(r >= 0);
			info.Module = builder.GetModule();
		}

		if (r != 0)
		{
			engine->DiscardModule(info.Module->GetName());
			info.Enabled = false;
		}

		++it;
	}

	// Pass 4: call Init of each function, so that light dependencies will resolve
	for (auto it = _modules.begin(); it != _modules.end(); ++it)
	{
		auto &info = (*it);

		if (!info.Enabled)
			continue;

		_currentInfo = &info;
		auto module = info.Module;
		current = module;
		auto func = module->GetFunctionByName("Init");
		auto ctx = engine->CreateContext();
		ctx->Prepare(func);
		ctx->SetArgObject(0, renderer);
		ctx->Execute();
		ctx->Release();

		wxFile file;
		file.Create("renderers\\" + info.Namespace + "\\__codecache.bin", true);
		if (wxFileExists("renderers\\" + info.Namespace + "\\__codecache.bin"))
		{
			wxFileStream fs("renderers\\" + info.Namespace + "\\__codecache.bin");
			wxasStreamWrapper wrapper(&fs);

			module->SaveByteCode(&wrapper, true);
		}
	}
}